/**
 * Copyright (C) 2010 DustedPixels.com
 */
package com.dustedpixels.cirt.model.cpu.z80;

import com.dustedpixels.cirt.model.core.Circuit;
import com.dustedpixels.cirt.model.core.In;
import com.dustedpixels.cirt.model.core.Internal;
import com.dustedpixels.cirt.model.core.Out;
import com.dustedpixels.cirt.model.core.Pins;
import com.dustedpixels.cirt.model.core.Script;

/**
 * Machine cycle controller.
 * 
 * TODO(micapolos): Interrupt handling
 * TODO(micapolos): A signal to increment PC
 * TODO(micapolos): A signal to put IR on the address bus
 * TODO(micapolos): A signal to increment R
 * TODO(micapolos): Implement bus control lines
 * TODO(micapolos): Implement HALT
 * 
 * @author Michal Pociecha-Los (micapolos@gmail.com)
 */
@Circuit
public final class MachineCycleController {
  // Requests start of a new machine cycle, sampled on CLK rise
  @In public boolean newCycle;
  @In public boolean fetchCycle;
  @In public boolean writeCycle;
  @In public boolean ioCycle;
  
  // Interrupt requests, sampled on CLK rise
  @In public boolean irq;
  @In public boolean nmi;
  
  // External WAIT signal, sampled on CLK fall
  @In public boolean wait;
  
  // Output lines
  @Out public boolean m1;
  @Out public boolean rfsh;
  @Out public boolean mreq;
  @Out public boolean iorq;
  @Out public boolean rd;
  @Out public boolean wr;
  
  // External buses
  @In @Pins(16) public int addressIn;
  @Out @Pins(16) public int addressOut;
  @Out public boolean addressOutActive;
  
  @In @Pins(8) public int dataIn;
  @Out @In @Pins(8) public int dataOut;
  @Out public boolean dataOutActive; 
  
  // Internal buses
  @In @Pins(16) public int internalAddressIn;
  @In @Pins(8) public int internalDataIn;
  @Out @Pins(8) public int internalDataOut;
  
  // Counter of M-cycle and T-cycle
  @Out public boolean duringFetchCycle;
  @Out @Pins(2) public int tCycle;
  @Out public boolean duringWait;
  
  // Request to latch opcode
  @Out public boolean opcodeWrite;
  
  // Internal latches
  @Internal private boolean fetchCycleLatch;
  @Internal private boolean writeCycleLatch;
  @Internal private boolean ioCycleLatch;
  @Internal @Pins(2) private int tCycleLatch;
  @Internal @Pins(2) private int implicitWaitCount;

  @Internal private boolean waitLatch;
  
  @Internal private boolean irqLatch;
  @Internal private boolean nmiLatch;
  
  @Internal private boolean mreqLatch;
  @Internal private boolean iorqLatch;
  @Internal private boolean rdLatch;
  @Internal private boolean wrLatch;
  
  @Internal @Pins(16) private int addressOutLatch;
  @Internal @Pins(8) private int dataOutLatch;
  @Internal public boolean dataOutActiveLatch;
  @Internal private boolean duringWaitLatch;
  @Internal private boolean opcodeWriteLatch;
  
  /**
   * Performs synchronous actions on clock rise.
   */
  @Script
  public void clkRise() {
    if (newCycle) {
      fetchCycleLatch = fetchCycle;
      writeCycleLatch = writeCycle;
      ioCycleLatch = ioCycle;
      tCycleLatch = 0;
      
      if (nmiLatch) {
        // TODO(micapolos): Handle NMI
      } else if (irqLatch) {
        // TODO(micapolos): Handle IRQ
      }
      
      // Calculate the number of implicit WAIT cycles
      // TODO(micapolos): IRQ cycle requires 2 implicit WAIT cycles. 
      implicitWaitCount = !fetchCycle && ioCycle ? 1 : 0;
      
      // Latch address from internal address bus at the beginning of every
      // machine cycle
      addressOutLatch = internalAddressIn;
      dataOutLatch = internalDataIn;
    } else {
      boolean endOfCycle = tCycleLatch == (fetchCycleLatch ? 3 : 2);
      
      // Do nothing if we are already inside last T cycle
      if (!endOfCycle) {
        // Handle internal and external WAIT signals at the end of every T2 cycle
        if (waitLatch && implicitWaitCount != 0) {
          implicitWaitCount--;
        }
        duringWaitLatch = waitLatch;
        
        // Handle cycle
        if (!duringWaitLatch) {
          tCycleLatch++;
          
          // Latch address from internal address bus at the beginning of T2
          // of fetch cycle - address bus should contain IR address at that
          // moment.
          if (fetchCycleLatch && tCycleLatch == 2) {
            addressOutLatch = internalAddressIn;
          }
        }
      }
    }
  }
  
  /**
   * Performs synchronous actions on clock fall.
   */
  @Script
  public void clkFall() {
    // Latch IRQ and NMI lines. They will be used just before new machine cycle
    // is started.
    irqLatch = irq;
    nmiLatch = nmi;
    waitLatch = (tCycleLatch == 1) && (wait || implicitWaitCount != 0);
    
    if (fetchCycleLatch) {
      switch (tCycleLatch) {
      case 0:
        mreqLatch = true;
        rdLatch = true;
        break;
      case 1:
        if (!waitLatch) {
          mreqLatch = false;
          rdLatch = false;
          opcodeWriteLatch = true;
        }
        break;
      case 2:
        opcodeWriteLatch = false;
        mreqLatch = true;
        break;
      case 3:
        mreqLatch = false;
        break;
      }
    } else {
      if (!ioCycleLatch) {
        if (!writeCycleLatch) {
          switch (tCycleLatch) {
          case 0:
            mreqLatch = true;
            rdLatch = true;
            break;
          case 1:
            break;
          case 2:
            mreqLatch = false;
            rdLatch = false;
            break;
          }
        } else {
          switch (tCycleLatch) {
          case 0:
            dataOutActiveLatch = true;
            mreqLatch = true;
            break;
          case 1:
            if (!waitLatch) {
              wrLatch = true;
            }
            break;
          case 2:
            mreqLatch = false;
            wrLatch = false;
            dataOutActiveLatch = false;
            break;
          }
        }
      } else {
        if (!writeCycleLatch) {
          switch (tCycleLatch) {
          case 0:
            iorqLatch = true;
            rdLatch = true;
            break;
          case 1:
            break;
          case 2:
            iorqLatch = false;
            rdLatch = false;
            break;
          }
        } else {
          switch (tCycleLatch) {
          case 0:
            dataOutActiveLatch = true;
            iorqLatch = true;
            wrLatch = true;
            break;
          case 1:
            break;
          case 2:
            dataOutActiveLatch = false;
            iorqLatch = false;
            wrLatch = false;
            break;
          }
        }
      }
    }
  }
  
  /**
   * Updates output signals with current state of this controller.
   */
  @Script
  public void update() {
    boolean extendMreqRd = (fetchCycleLatch && tCycleLatch == 1);
    boolean trimIorqRdWr = !fetchCycleLatch && ioCycleLatch && tCycleLatch == 0;
    
    addressOut = addressOutLatch;
    addressOutActive = true;
    
    dataOut = dataOutLatch;
    dataOutActive = dataOutActiveLatch || (!fetchCycleLatch && writeCycleLatch && tCycleLatch == 2);
    
    internalDataOut = dataIn;
    
    opcodeWrite = opcodeWriteLatch;
    duringFetchCycle = fetchCycleLatch;
    tCycle = tCycleLatch;
    duringWait = duringWaitLatch;

    mreq = mreqLatch || extendMreqRd;
    iorq = iorqLatch && !trimIorqRdWr;
    rd = (rdLatch || extendMreqRd) && !trimIorqRdWr;
    wr = wrLatch && !trimIorqRdWr;
    m1 = fetchCycleLatch && ((tCycleLatch & 0x02) == 0);
    rfsh = fetchCycleLatch && !m1;
  } 
}
